﻿# define _CRT_SECURE_NO_WARNINGS 1
//深入理解指针3，4
//1. 字符指针变量
//在指针的类型中我们知道有⼀种指针类型为字符指针 char*; 
//⼀般使⽤:
#include <stdio.h>
//int main()
//{
//	char ch = 'w';
//	char* p = &ch;
//	printf("%c", *p);
//	return 0;
//}
//还有⼀种使⽤⽅式如下：
//int main()
//{
//	const char* p = "hellow wold";
//	printf("%c", *p);
//	return 0;
//}
//代码 const char* pstr = "hello wold"; 特别容易让同学以为是把字符串 hello wold 放
//到字符指针 p ⾥了，但是本质是把字符串 hello wold⾸字符h的地址放到了p中。
//上⾯代码的意思是把⼀个常量字符串的⾸字符 h 的地址存放到指针变量 pstr 中。
//《剑指offer》中收录了⼀道和字符串相关的笔试题，我们⼀起来学习⼀下：
//int main()
//{
//	char str1[] = "hello bit.";
//	char str2[] = "hello bit.";
//	const char* str3 = "hello bit.";
//	const char* str4 = "hello bit.";
//	if (str1 == str2)
//		printf("str1 and str2 are same\n");
//	else
//		printf("str1 and str2 are not same\n");
//
//	if (str3 == str4)
//		printf("str3 and str4 are same\n");
//	else
//		printf("str3 and str4 are not same\n");
//
//	return 0;
//}
//输出结果:
//str1 and str2 are not same
//str3 and str4 are same
//这⾥str3和str4指向的是⼀个同⼀个常量字符串。C / C++会把常量字符串存储到单独的⼀个内存区域，
//当⼏个指针指向同⼀个字符串的时候，他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始
//化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同，str3和str4相同。
//2. 数组指针变量
//2.1 数组指针变量是什么
//那数组指针变量应该是：存放的应该是数组的地址，能够指向数组的指针变量。
//下⾯代码哪个是数组指针变量？
//int* p1[10];
//int (*p2)[10];
//解释：p先和* 结合，说明p是⼀个指针变量，然后指针指向的是⼀个⼤⼩为10个整型的数组。所以p是
//⼀个指针，指向⼀个数组，叫数组指针。
//这⾥要注意：[]的优先级要⾼于* 号的，所以必须加上（）来保证p先和* 结合。
//int main()
//{
//	int arr[10] = { 0 };
//	int(*p)[10] = &arr;
//	//数组指针变量组成部分解释
//	//int:p指向数组元素的类型
//	//(*p)：p是数组指针变量名
//	//[10]:p指向数组的元素个数
//	return 0;
//}
//3. ⼆维数组传参的本质
//有了数组指针的理解，我们就能够讲⼀下⼆维数组传参的本质了。
//过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候，我们是这样写的
//void print_arr(int arr[3][5], int x, int y)
//{
//	for (int i = 0; i < x; i++)
//	{
//		for (int j = 0; j < y; j++)
//		{
//			printf("%d ",arr[i][j]);
//		}
//		printf("\n");
//	}
//}
//int main()
//{
//	int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
//	print_arr(arr, 3, 5);//二维数组传参时要传递行数，列数
//	return 0;
//
//}
//这⾥实参是⼆维数组，形参也写成⼆维数组的形式，那还有什么其他的写法吗？
//⾸先我们再次理解⼀下⼆维数组，⼆维数组其实可以看做是每个元素是⼀维数组的数组，也就是⼆维
//数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏，是个⼀维数组。
//void print_arr(int (*p)[5], int x, int y)
//{
//	for (int i = 0; i < x; i++)
//	{
//		for (int j = 0; j < y; j++)
//		{
//			printf("%d ", *(*(p+i)+j));
//		}
//		printf("\n");
//	}
//}
//int main()
//{
//	int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
//	print_arr(arr, 3, 5);//二维数组传参时要传递行数，列数
//	return 0;
//
//}
//总结：⼆维数组传参，形参的部分可以写成数组，也可以写成指针形式。
//4. 函数指针变量
//4.1 函数指针变量的创建
//什么是函数指针变量呢？
//根据前⾯学习整型指针，数组指针的时候，我们的类⽐关系，我们不难得出结论：
////函数指针变量应该是⽤来存放函数地址的，未来通过地址能够调⽤函数的。
//void test()
//{
//	printf("hehe\n");
//}
//int main()
//{
//	printf("%p\n", test);
//	printf("%p\n", &test);
//    return 0;
//
//}
//输出结果：
//00007FF7294313D9
//00007FF7294313D9
//确实打印出来了地址，所以函数是有地址的，函数名就是函数的地址，当然也可以通过 & 函数名 的⽅
//式获得函数的地址。
//void test()
//{
//	printf("hehe\n");
//}
//int add(int x, int y)
//{
//	return x + y;
//}
//int main()
//{
//	void (*p1)() = test;
//	void (*p1)() = &test;
//	int(*p)(int, int) = &add;
//	int(*p)(int x, int y) = add;
//	//*p:表示p是一个函数指针变量，
//	//int ,int :表示p是指向的函数的两个参数是int，int 类型
//	//最前面的int:表示表示p是指向的函数的返回类型是int类型
//	//int(*)(int,int) ://p函数指针变量的类型 
//	return 0;
//}
//4.2 函数指针变量的使⽤
//通过函数指针调⽤指针指向的函数。
//int add(int x, int y)
//{
//	return x + y;
//}
//int main()
//{
//	int(*p)(int, int) = &add;
//	/*int(*p)(int x, int y) = add;*/
//	printf("%d\n", (*p)(3, 5));
//	printf("%d\n", p(99, 99));
//	return 0;
//}
//4.3 两段有趣的代码
//代码1
/*int main()
{
	(*(void (*)())0)();
	 void (*signal(int, void(*)(int)))(int);
	 return 0;

}
代码解析：
1. 上述代码其实是⼀次函数调⽤，调⽤的是0地址处的⼀个函数，这个函数没有参数，没有返回值。
2. 代码中的 void (*)() 是函数指针类型，(void (*)())0 类型放在括号中意思是强制类型转
化，是将0这个整型值，强制类型转化成这种函数指针类型，也就是说0被当做函数的地址了。
3. * (void (*)())0 ，前⾯加⼀个 * ，就是调⽤0地址处的这个函数，根据函数指针的类型能知
道，这个函数没有参数，也没有返回值。
代码解析：
1. 上述代码是⼀次函数的声明。
2. 声明的函数名字叫 signal ，函数的参数有2个，第⼀个是int类型，第⼆个是函数指针类
型 : void(*)(int) 。函数的返回值也是函数指针 void(*)(int) 。
4.3.1 typedef关键字
typedef是⽤来类型重命名的，可以将复杂的类型，简单化。
⽐如，你觉得 unsigned int 写起来不⽅便，如果能写成 uint 就⽅便多了，那么我们可以使⽤*/
//typedef unsigned int uint;
//typedef int* p_;
//int main()
//{
//	unsigned int a = 30;
//	uint b = 20;
//	int c = 10;
//	int* p = &c;
//	int d = 40;
//	p_ p2 = &d;
//	return 0;
//}
//但是对于数组指针和函数指针稍微有点区别：
//⽐如我们有数组指针类型 int(*)[5], 需要重命名为 parr_t ，那可以这样写：
//1 typedef int(*parr_t)[5]; //新的类型名必须在*的右边 
//函数指针类型的重命名也是⼀样的，⽐如，将 void(*)(int) 类型重命名为 pf_t, 就可以这样写：
//typedef int(*prt_)[10];
//typedef int(*prm_)(int, int);
//int add(int x, int y)
//{
//	return x + y;
//}
//int main()
//{
//	int arr[10] = { 0 };
//	int(*p1)[10] = &arr;
//	prt_ p12 = &arr;
//	int(*p2)(int, int) = add;
//	prm_ p22 = &add;
//	return 0;
//}
//5. 函数指针数组
//数组是⼀个存放相同类型数据的存储空间，我们已经学习了指针数组，
//⽐如：
//int* arr[10];
////数组的每个元素是int* 
//1
//2
//那要把函数的地址存到⼀个数组中，那这个数组就叫函数指针数组，那函数指针的数组如何定义呢？
//int (*parr1[3])();
//int* parr2[3]();
//int (*)() parr3[3];
//
//答案是：parr1
//parr1 先和[] 结合，说明parr1是数组，数组的内容是什么呢？
//是 int (*)() 类型的函数指针。
//int add(int x, int y)
//{
//	return x + y;
//}
//int sub(int x, int y)
//{
//	return x - y;
//}
//int main()
//{
//	int (*p[])() = { add,sub };//这就是函数指针数组
//}
//6. 转移表
//函数指针数组的⽤途：转移表
////举例：计算器的⼀般实现：
//void menu()
//{
//	printf("******************************\n");
//	printf("******1.add********2.sub******\n");
//	printf("******3.mul********4.div******\n");
//	printf("************0.exit************\n");
//	printf("******************************\n");
//}
//int add(int x, int y)
//{
//	return x + y;
//}
//int sub(int x, int y)
//{
//	return x - y;
//}
//int mul(int x, int y)
//{
//	return x * y;
//}
//int div(int x, int y)
//{
//	return x / y;
//}
//int main()
//{
//	int x = 0;
//	int y = 0;
//	int input = 1;
//	int r = 0;
//	do
//	{
//		menu();
//		printf("请选择:");
//		scanf("%d", &input);
//		switch (input)
//		{
//			case 0:
//				printf("退出计算器");
//				break;
//			case 1:
//				printf("请输入操作数:");
//				scanf("%d %d", &x, &y);
//			    r = add(x, y);
//				printf("%d\n", r);
//				break;
//			case 2: 
//				printf("请输入操作数:");
//				scanf("%d %d", &x, &y);
//				r = sub(x, y);
//				printf("%d\n", r);
//				break;
//			case 3:
//				printf("请输入操作数:");
//				scanf("%d %d", &x, &y);
//				r = mul(x, y);
//				printf("%d\n", r);
//				break;
//			case 4:
//				printf("请输入操作数:");
//				scanf("%d %d", &x, &y);
//				r = div(x, y);
//				printf("%d\n", r);
//				break;
//			defult:
//				printf("输入错误，请重新输入");
//					break;
//		}
//	} while (input);
//	return 0;
//}
//使⽤函数指针数组的实现：
void menu()
{
	printf("******************************\n");
	printf("******1.add********2.sub******\n");
	printf("******3.mul********4.div******\n");
	printf("************0.exit************\n");
	printf("******************************\n");
}
int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int mul(int x, int y)
{
	return x * y;
}
int div(int x, int y)
{
	return x / y;
}
int main()
{
	int y = 0;
	int x = 0;
	int input = 1;
	int r = 0;
	int (*p[5])(int x,int y) = { 0,add,sub,mul,div };
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		if (input <= 4 && input >= 1)
		{
			printf("请输入操作数：");
			scanf("%d %d", &x, &y);
			r = (*p[input])(x, y);
			printf("%d\n", r);
		}
		else if (input == 0)
			printf("退出计算器");
		else
		{
			printf("输入错误，请重新输入\n");
		}
	} while (input);
	return 0;
}